# coding=utf-8

import wx
import os
import time
import serial
import binascii
import datetime
import serial.tools.list_ports as list_ports

global echo
echo = False
sendData = []

stopbitsList = ['0', '1', '2']
databitsList = ['5', '6', '7', '8']
checkbitsList = ['None', 'Old', 'Even']
baudrateList = ['300', '600', '1200', '2400', '4800', '9600', '19200', '38400', '115200']

Serial = serial.Serial(port=None, baudrate=9600, bytesize=8, parity='E', stopbits=1, timeout=0)

class ParamsFrame(wx.Panel):
    '''
    串口参数配置:
       1.包括端口号,波特率,数据位,校验位,停止位和开关按钮;
       2.点击端口号的下拉框时自动检测当前有效的端口号名称.
       3.串口打开失败会弹出错误对话框.
    '''

    def __init__(self, parent):
        wx.Panel.__init__(self, parent)
        StaticBox = wx.StaticBox(self, -1, u"串口配置")
        sizer = wx.StaticBoxSizer(StaticBox, wx.HORIZONTAL)

        portLable = wx.StaticText(StaticBox, -1, u"端口号:")
        self.port = wx.ComboBox(StaticBox, -1, "", wx.DefaultPosition, (80, -1), "", wx.CB_DROPDOWN)
        self.CheckValidPorts(None)  # 初始化检查有效的端口号

        baudrateLable = wx.StaticText(StaticBox, -1, u"波特率:")
        self.baudrate = wx.ComboBox(StaticBox, -1, "9600", wx.DefaultPosition, (80, -1), baudrateList, wx.CB_DROPDOWN)

        parityLable = wx.StaticText(StaticBox, -1, u"校验位:")
        self.parity = wx.ComboBox(StaticBox, -1, "Even", wx.DefaultPosition, (70, -1), checkbitsList, wx.CB_DROPDOWN)

        bytesizeLable = wx.StaticText(StaticBox, -1, u"数据位:")
        self.bytesize = wx.ComboBox(StaticBox, -1, "8", wx.DefaultPosition, (70, -1), databitsList, wx.CB_DROPDOWN)

        stopbitsLable = wx.StaticText(StaticBox, -1, u"停止位:")
        self.stopbits = wx.ComboBox(StaticBox, -1, "1", wx.DefaultPosition, (70, -1), stopbitsList, wx.CB_DROPDOWN)

        sizer.Add(portLable, 0, wx.ALIGN_CENTER_VERTICAL)
        sizer.Add(self.port, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 15)

        sizer.Add(baudrateLable, 0, wx.ALIGN_CENTER_VERTICAL)
        sizer.Add(self.baudrate, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 15)

        sizer.Add(parityLable, 0, wx.ALIGN_CENTER_VERTICAL)
        sizer.Add(self.parity, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 15)

        sizer.Add(bytesizeLable, 0, wx.ALIGN_CENTER_VERTICAL)
        sizer.Add(self.bytesize, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 15)

        sizer.Add(stopbitsLable, 0, wx.ALIGN_CENTER_VERTICAL)
        sizer.Add(self.stopbits, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 45)

        self.switchBtn = wx.Button(StaticBox, -1, u"打开串口")
        sizer.Add(self.switchBtn)
        self.SetSizer(sizer)

        self.Bind(wx.EVT_COMBOBOX_DROPDOWN, self.CheckValidPorts, self.port)
        self.Bind(wx.EVT_BUTTON, self.SwitchPort, self.switchBtn)

    def CheckValidPorts(self, evt):
        GetItems = self.port.GetItems()

        Items_list = []
        for buf in list(list_ports.comports()):
            if buf[0].find('usbserial') != -1:
                Items_list.append(buf[0])

        if len(Items_list) == 0:
            Items_list.append('None')

        if GetItems != Items_list:
            self.port.Clear()
            self.port.SetItems(Items_list)
            self.port.Select(0)

    def SwitchPort(self, evt):
        Serial.port = self.port.GetValue().encode('utf-8')
        Serial.baudrate = int(self.baudrate.GetValue())
        Serial.parity = self.parity.GetValue()[0].encode('utf-8')
        Serial.bytesize = int(self.bytesize.GetValue())
        Serial.stopbits = int(self.stopbits.GetValue())

        if self.switchBtn.GetLabel() == u'打开串口':
            try:
                Serial.open()
                self.switchBtn.SetLabel(u'关闭串口')
            except:
                dlg = wx.MessageDialog(None, u'打开失败,请检查串口是否有效或被占用!', u'错误提示:\n', wx.OK | wx.ICON_ERROR)
                dlg.ShowModal()
        else:
            Serial.close()
            self.switchBtn.SetLabel(u'打开串口')


class RecvFrame(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent, -1)
        StaticBox = wx.StaticBox(self, -1, u"接收数据")
        sizer = wx.StaticBoxSizer(StaticBox, wx.VERTICAL)

        configSizer = wx.FlexGridSizer(rows=1, hgap=5, vgap=5)

        self.hexDisplay = wx.CheckBox(StaticBox, -1, u"十六进制")
        configSizer.Add(self.hexDisplay, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 10)

        self.stopRecv = wx.CheckBox(StaticBox, -1, u"暂停接收")
        configSizer.Add(self.stopRecv, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 10)

        self.addTime = wx.CheckBox(StaticBox, -1, u"增加时标")
        self.addTime.SetValue(True)
        configSizer.Add(self.addTime, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 10)

        self.saveLog = wx.CheckBox(StaticBox, -1, u"保存记录")
        configSizer.Add(self.saveLog, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 22)

        recvTimeoutLable = wx.StaticText(StaticBox, -1, u"接收超时(ms):")
        configSizer.Add(recvTimeoutLable, 0, wx.ALIGN_CENTER_VERTICAL)

        self.recvTimeout = wx.TextCtrl(StaticBox, -1, "50", size=(45, -1), style=wx.TE_CENTER)
        configSizer.Add(self.recvTimeout, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 25)

        recvBytesNumLable = wx.StaticText(StaticBox, -1, u"接收字节:")
        configSizer.Add(recvBytesNumLable, 0, wx.ALIGN_CENTER_VERTICAL)

        self.recvBytesNum = wx.StaticText(StaticBox, -1, "0", size=(50, -1))
        configSizer.Add(self.recvBytesNum, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 5)

        clearBytesBtn = wx.Button(StaticBox, -1, u"重置计数")
        configSizer.Add(clearBytesBtn, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 15)

        clearDataBtn = wx.Button(StaticBox, -1, u"清空数据")
        configSizer.Add(clearDataBtn, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 5)

        sizer.Add(configSizer, 0, wx.EXPAND | wx.BOTTOM, 5)

        self.recvText = wx.TextCtrl(StaticBox, -1, style=wx.TE_MULTILINE | wx.TE_RICH2)
        sizer.Add(self.recvText, 1, wx.EXPAND | wx.RIGHT, 2)
        self.SetSizer(sizer)

        self.Bind(wx.EVT_BUTTON, self.ClearRecvBytesNum, clearBytesBtn)
        self.Bind(wx.EVT_BUTTON, self.ClearRecvData, clearDataBtn)

        self.timer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.recvData, self.timer)
        self.timer.Start(1)

        self.readingData = False
        self.lastReadTime = 0.0

    def recvData(self, evt):
        global echo
        if Serial.isOpen():
            if echo and len(sendData):
                if not self.stopRecv.IsChecked():
                    if self.addTime.IsChecked():  # 判断是否增加时标
                        recvTime = time.strftime("[%H:%M:%S ", time.localtime()) + \
                                   str(datetime.datetime.now().microsecond)[0:3] + "] Tx: "
                    else:
                        recvTime = "Tx: "

                    tmp_str = sendData[0].decode('gbk')
                    self.recvText.AppendText(recvTime + tmp_str)
                    if self.saveLog.IsChecked():
                        self.SaveDataLog(recvTime.encode('utf8') + tmp_str.encode('utf-8'))

                    x = self.recvText.GetInsertionPoint()
                    self.recvText.SetStyle(x - len(tmp_str) - len(recvTime), x - 1, wx.TextAttr('blue'))
                    sendData.pop()
                    if len(sendData):
                        sendData.remove(sendData[0])
                else:
                    sendData.pop()

            tmp_buf = ''
            recvTime = ''
            data = Serial.readall()
            dataLen = len(data)
            if dataLen != 0:
                if not self.readingData:
                    if self.addTime.IsChecked():  # 判断是否增加时标
                        recvTime = time.strftime("[%H:%M:%S ", time.localtime()) + \
                                   str(datetime.datetime.now().microsecond)[0:3] + "] Rx: "
                    else:
                        recvTime = "Rx: "
                else:
                    recvTime = ''

                self.readingData = True
                self.lastReadTime = time.time()

                if self.hexDisplay.IsChecked():  # 数据格式,判断是否显示16进制
                    data = binascii.b2a_hex(data)
                    for i in range(len(data) / 2):
                        tmp_buf += data[2 * i: 2 * (i + 1)] + ' '
                else:
                    tmp_buf = data.decode('gbk', errors='replace')
                    tmp_buf = tmp_buf.encode('utf-8', errors='replace')

            if self.readingData:
                timeout = float(self.recvTimeout.GetValue()) / 1000
                if time.time() - self.lastReadTime > timeout:  # 判断接收帧间隔时间,若超出判定接收完成并换行
                    self.readingData = False
                    tmp_buf += '\r'

            if not self.stopRecv.IsChecked() and len(tmp_buf) != 0:
                if len(recvTime):
                    self.recvText.AppendText(recvTime)
                try:
                    self.recvText.AppendText(tmp_buf)
                    # x = self.recvText.GetInsertionPoint()
                    # self.recvText.SetStyle(x - len(tmp_buf) - len(recvTime), x, wx.TextAttr('white', 'black'))
                except:
                    pass

                if self.saveLog.IsChecked():
                    self.SaveDataLog(recvTime.encode('utf8') + tmp_buf)

                if tmp_buf != '\r':
                    self.recvBytesNum.Label = str(int(self.recvBytesNum.Label) + dataLen)  # 累计接收数据的个数

    def ClearRecvBytesNum(self, event):
        self.recvBytesNum.Label = "0"

    def ClearRecvData(self, event):
        self.recvText.Clear()

    def SaveDataLog(self, data):
        log_dir = os.getcwd() + '/log'
        if not os.path.exists(log_dir):
            os.makedirs(log_dir)
        file_name = log_dir + '/' + time.strftime("%Y%m%d", time.localtime())
        fp = open(file_name, 'a+')
        fp.write(data)
        fp.close()


class SendFrame(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent, -1)
        comBox = wx.StaticBox(self, -1, u"发送数据")
        sizer = wx.StaticBoxSizer(comBox, wx.VERTICAL)

        configSizer = wx.FlexGridSizer(rows=1, hgap=5, vgap=5)

        self.hexCheckBox = wx.CheckBox(comBox, -1, u"十六进制")
        configSizer.Add(self.hexCheckBox, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 10)

        self.echo = wx.CheckBox(comBox, -1, u"回显发送")
        configSizer.Add(self.echo, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 10)

        self.reSendStart = wx.CheckBox(comBox, -1, u"启动重发")
        configSizer.Add(self.reSendStart, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 10)

        reSendCntLable = wx.StaticText(comBox, -1, u"重发次数:")
        configSizer.Add(reSendCntLable, 0, wx.ALIGN_CENTER_VERTICAL)

        self.reSendCnt = wx.TextCtrl(comBox, -1, "0", size=(38, -1), style=wx.TE_CENTER)
        configSizer.Add(self.reSendCnt, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 10)

        reSendIntervalLable = wx.StaticText(comBox, -1, u"重发间隔(ms):")
        configSizer.Add(reSendIntervalLable, 0, wx.EXPAND)

        self.reSendTime = wx.TextCtrl(comBox, -1, "1000", size=(45, -1), style=wx.TE_CENTER)
        configSizer.Add(self.reSendTime, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 10)

        recvBytesNumLable = wx.StaticText(comBox, -1, u"发送字节:")
        configSizer.Add(recvBytesNumLable, 0, wx.ALIGN_CENTER_VERTICAL)

        self.sendBytesNum = wx.StaticText(comBox, -1, "0", size=(50, -1))
        configSizer.Add(self.sendBytesNum, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 4)

        clearBytesNumBtn = wx.Button(comBox, -1, u"重置计数")
        configSizer.Add(clearBytesNumBtn, 0, wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 15)

        sendDataBtn = wx.Button(comBox, -1, u"发送数据")
        configSizer.Add(sendDataBtn)

        sizer.Add(configSizer, 0, wx.EXPAND | wx.BOTTOM, 5)

        self.SendText = wx.TextCtrl(comBox, -1, "", style=wx.TE_MULTILINE | wx.TE_RICH2)
        sizer.Add(self.SendText, 1, wx.EXPAND | wx.RIGHT, 2)
        self.SetSizer(sizer)

        self.Bind(wx.EVT_BUTTON, self.ClearSendBytesNum, clearBytesNumBtn)
        self.Bind(wx.EVT_BUTTON, self.StartSendData, sendDataBtn)
        self.Bind(wx.EVT_CHECKBOX, self.StartResend, self.reSendStart)
        self.Bind(wx.EVT_CHECKBOX, self.SendDataMode, self.hexCheckBox)
        self.Bind(wx.EVT_CHECKBOX, self.EchoSendData, self.echo)
        # self.Bind(wx.EVT_TEXT, self.SendTextMode, self.SendText)

        self.SendText.Bind(wx.EVT_LEFT_DOWN, self.test)

        self.time = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.reSendTask, self.time)
        self.SendCount = 0

        # acceltbl = wx.AcceleratorTable([(wx.ACCEL_SHIFT, wx.WXK_F5, self.EchoSendData().GetId())])
        # self.SetAcceleratorTable(acceltbl)

    def test(self, evt):
        evt.Skip()

    def SendTextMode(self, evt):
        pass

    def SendDataMode(self, evt):
        data = self.SendText.GetValue().encode("gbk")
        if self.hexCheckBox.IsChecked():
            data = binascii.b2a_hex(data)
            tmp_str = ''
            for i in range(len(data) / 2):
                tmp_str += data[2 * i: 2 * (i + 1)]
                if i != len(data) / 2 - 1:
                    tmp_str += ' '
            self.SendText.Clear()
            self.SendText.write(tmp_str)
        else:
            try:
                data = data.replace(' ', '')
                data = binascii.a2b_hex(data)
                data = data.decode("gbk")
                self.SendText.Clear()
                self.SendText.write(data)
            except Exception, e:
                dlg = wx.MessageDialog(None, str(e), u'Error:\n', wx.OK | wx.ICON_ERROR)
                dlg.ShowModal()

    def EchoSendData(self, evt):
        global echo
        if self.echo.IsChecked():
            echo = True
        else:
            echo = False

    def StartResend(self, evt):
        if not self.reSendStart.IsChecked():
            self.time.Stop()

    def ClearSendBytesNum(self, evt):
        self.sendBytesNum.Label = "0"

    def StartSendData(self, evt):
        if Serial.isOpen():
            if self.reSendStart.IsChecked():
                self.SendCount = 1
                tmp = int(self.reSendTime.GetValue())
                self.time.Start(tmp)
            self.SendSerialData()
        else:
            dlg = wx.MessageDialog(None, u'串口没有被打开,不能发送数据!', u'错误提示:\n', wx.OK | wx.ICON_ERROR)
            dlg.ShowModal()

    def SendSerialData(self):
        global echo
        data = self.SendText.GetValue().encode("gbk")
        if echo and len(data):
            sendData.append(data + '\r')  # 添加发送数据到列表

        if self.hexCheckBox.IsChecked():  # 十六进制发送,转换数据编码
            data = data.replace(' ', '').decode('hex')

        if len(data):
            Serial.write(data)
            self.sendBytesNum.Label = str(int(self.sendBytesNum.Label) + len(data))  # 累计发送数据的个数
        else:
            dlg = wx.MessageDialog(None, u'发送区未输入要发送的数据!', u'告警提示:\n', wx.OK | wx.ICON_ERROR)
            dlg.ShowModal()

    def reSendTask(self, evt):
        if not Serial.isOpen():
            self.time.Stop()
        else:
            TotalCount = int(self.reSendCnt.GetValue())
            self.SendCount += 1
            if self.SendCount >= TotalCount and TotalCount != 0:
                self.time.Stop()
            else:
                self.SendSerialData()

class SplitterFrame(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)

        sp = wx.SplitterWindow(self, style=wx.SP_3DSASH | wx.SP_LIVE_UPDATE)
        p1 = RecvFrame(sp)
        p2 = SendFrame(sp)
        sp.SetMinimumPaneSize(200)
        sp.SplitHorizontally(p1, p2)
        sp.SetSashGravity(1)

        sizer = wx.BoxSizer()
        sizer.Add(sp, 1, wx.EXPAND)
        self.SetSizer(sizer)

class SerialFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, -1, "Serial Tool")
        configFrame = ParamsFrame(self)
        spFrame = SplitterFrame(self)

        Sizer = wx.BoxSizer(wx.VERTICAL)
        Sizer.Add(configFrame, 0, wx.EXPAND | wx.ALL, 0)
        Sizer.Add(wx.StaticLine(self), 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 3)
        Sizer.Add(spFrame, 1, wx.EXPAND | wx.ALL, 0)
        self.SetSizer(Sizer)
        Sizer.Fit(self)

        self.statusbar = self.CreateStatusBar()
        self.statusbar.SetFieldsCount(4)
        self.statusbar.SetStatusWidths([-7, -1, -1, -1])

        self.statusbar.SetStatusText(u"就绪", 0)
        self.statusbar.SetStatusText(u"长度: 0", 1)
        self.statusbar.SetStatusText(u"行: 0", 2)
        self.statusbar.SetStatusText(u"列: 0", 3)

        self.Bind(wx.EVT_CLOSE, self.CloseWindows)

    def CloseWindows(self, evt): #关闭窗口的事件处理函数
        self.Destroy()
        if Serial.isOpen():
            Serial.close()

if __name__ == "__main__":
    app = wx.App()
    frame = SerialFrame()
    frame.Show()
    app.MainLoop()
